Manejo y visualización de datos

Importar, transformar y visualizar datos en R

Manejo de datos

La exploración de datos nos permite verificar su calidad, entender los datos con los que trabajamos. Visualizar nuestros datos es un buen comienzo, pero a menudo vamos a necesitar transformar los datos previamente.

Formato tidy data (long)

Cada variable tiene su propia columna - cada observación tiene su propia fila - cada valor tiene su propia celda

Ejemplo organización “tidy”

Buenas prácticas en el manejo de datos

  • Las variables van en columnas (e.g. mediciones: altura, peso, sexo)
  • Tabla del análisis de la varianza
  • Las observaciones cada una en una fila (e.g. individuos de pingüinos)
  • Evitar espacios, números, y caracteres especiales en los nombres de columnas.
  • Siempre anotar valores de cero, para diferenciarlos de datos faltantes.
  • Usar celdas vacías o con NA para datos faltantes.
  • Las fechas incluirlas en columnas separadas como year, month, day.
  • No combinar varias informaciones en una misma celda. EJEMPLOS
  • Realiza todas las manipulaciones de datos mediante código para dejar constancia de los cambios.
  • Exporta los datos como texto plano (txt, csv)

Organizacion tabla

Hertz & McNeill 2024

dplyr y ggplot

coomo funcionan, libreria tidyverse.

Pipes: cómo encadenar operaciones

Los “pipes” permiten escribir código paso a paso, de forma legible:

library(dplyr)

# El propio de dplyr
iris %>%
  filter(Sepal.Length > 5) %>%
  summarise(promedio = mean(Sepal.Width))
  promedio
1 3.048305
# (control  (command) + shift + M)

# el de R desde hace dos años
iris |>
  filter(Sepal.Length > 5) |>
  summarise(promedio = mean(Sepal.Width))
  promedio
1 3.048305

Ejemplo datos de pingüinos

# 1. Cargar librerías

library(dplyr)
library(ggplot2)
library(readr)
library(palmerpenguins)
library(scico)

# 2. Leer datos
data(package = 'palmerpenguins')
datos <- penguins

# 3. Explorar

head(datos)
# A tibble: 6 × 8
  species island    bill_length_mm bill_depth_mm flipper_length_mm body_mass_g
  <fct>   <fct>              <dbl>         <dbl>             <int>       <int>
1 Adelie  Torgersen           39.1          18.7               181        3750
2 Adelie  Torgersen           39.5          17.4               186        3800
3 Adelie  Torgersen           40.3          18                 195        3250
4 Adelie  Torgersen           NA            NA                  NA          NA
5 Adelie  Torgersen           36.7          19.3               193        3450
6 Adelie  Torgersen           39.3          20.6               190        3650
# ℹ 2 more variables: sex <fct>, year <int>
str(datos)
tibble [344 × 8] (S3: tbl_df/tbl/data.frame)
 $ species          : Factor w/ 3 levels "Adelie","Chinstrap",..: 1 1 1 1 1 1 1 1 1 1 ...
 $ island           : Factor w/ 3 levels "Biscoe","Dream",..: 3 3 3 3 3 3 3 3 3 3 ...
 $ bill_length_mm   : num [1:344] 39.1 39.5 40.3 NA 36.7 39.3 38.9 39.2 34.1 42 ...
 $ bill_depth_mm    : num [1:344] 18.7 17.4 18 NA 19.3 20.6 17.8 19.6 18.1 20.2 ...
 $ flipper_length_mm: int [1:344] 181 186 195 NA 193 190 181 195 193 190 ...
 $ body_mass_g      : int [1:344] 3750 3800 3250 NA 3450 3650 3625 4675 3475 4250 ...
 $ sex              : Factor w/ 2 levels "female","male": 2 1 1 NA 1 2 1 2 NA NA ...
 $ year             : int [1:344] 2007 2007 2007 2007 2007 2007 2007 2007 2007 2007 ...
summary(datos)
      species          island    bill_length_mm  bill_depth_mm  
 Adelie   :152   Biscoe   :168   Min.   :32.10   Min.   :13.10  
 Chinstrap: 68   Dream    :124   1st Qu.:39.23   1st Qu.:15.60  
 Gentoo   :124   Torgersen: 52   Median :44.45   Median :17.30  
                                 Mean   :43.92   Mean   :17.15  
                                 3rd Qu.:48.50   3rd Qu.:18.70  
                                 Max.   :59.60   Max.   :21.50  
                                 NA's   :2       NA's   :2      
 flipper_length_mm  body_mass_g       sex           year     
 Min.   :172.0     Min.   :2700   female:165   Min.   :2007  
 1st Qu.:190.0     1st Qu.:3550   male  :168   1st Qu.:2007  
 Median :197.0     Median :4050   NA's  : 11   Median :2008  
 Mean   :200.9     Mean   :4202                Mean   :2008  
 3rd Qu.:213.0     3rd Qu.:4750                3rd Qu.:2009  
 Max.   :231.0     Max.   :6300                Max.   :2009  
 NA's   :2         NA's   :2                                 
unique(datos$island)
[1] Torgersen Biscoe    Dream    
Levels: Biscoe Dream Torgersen
datos_filtrados <- datos %>% filter (year == 2007)

datos_filtrados <-datos %>% filter (year == 2007, sex=="female")

datos_filtrados <- datos %>% filter (year == 2007, sex=="female", island %in% c("Dream", "Bicoe")) 
# island == c("Dream", "Bicoe")) NO VALE!
# summarize
head (datos_filtrados, 3)
# A tibble: 3 × 8
  species island bill_length_mm bill_depth_mm flipper_length_mm body_mass_g
  <fct>   <fct>           <dbl>         <dbl>             <int>       <int>
1 Adelie  Dream            39.5          16.7               178        3250
2 Adelie  Dream            39.5          17.8               188        3300
3 Adelie  Dream            36.4          17                 195        3325
# ℹ 2 more variables: sex <fct>, year <int>

Limpiar los datos

# 4. Limpiar: eliminar filas con NA
datos_limpios <- datos %>% 
  filter(!is.na(body_mass_g))

head(datos_limpios, 4)
# A tibble: 4 × 8
  species island    bill_length_mm bill_depth_mm flipper_length_mm body_mass_g
  <fct>   <fct>              <dbl>         <dbl>             <int>       <int>
1 Adelie  Torgersen           39.1          18.7               181        3750
2 Adelie  Torgersen           39.5          17.4               186        3800
3 Adelie  Torgersen           40.3          18                 195        3250
4 Adelie  Torgersen           36.7          19.3               193        3450
# ℹ 2 more variables: sex <fct>, year <int>

Transformar variables

# 5. Transformar: crear nueva variable
datos_limpios_kg <- datos_limpios %>%
  mutate(body_mass_kg = body_mass_g / 1000)

Ejemplo datos inscripción curso

library(here)
library(tidyverse)
library(tm)
library(wordcloud2)
library(readxl)

# se puede importar tablas desde excel, csv, ggsheets, txt...

datos_curso = read_excel(here("data/datos-curso.xlsx"))
# ifselse en dplyr?
# Vemos un resumen
str (datos_curso)
tibble [19 × 15] (S3: tbl_df/tbl/data.frame)
 $ Id                                                                    : num [1:19] 1 2 3 4 5 6 7 8 9 10 ...
 $ Hora de inicio                                                        : POSIXct[1:19], format: "2025-10-14 08:29:14" "2025-10-14 08:42:03" ...
 $ Hora de finalización                                                  : POSIXct[1:19], format: "2025-10-14 08:37:19" "2025-10-14 08:44:21" ...
 $ Correo electrónico                                                    : chr [1:19] "anonymous" "anonymous" "anonymous" "anónimo" ...
 $ Nombre                                                                : logi [1:19] NA NA NA NA NA NA ...
 $ Comentarios: DNI o documento                                          : logi [1:19] NA NA NA NA NA NA ...
 $ Curso de la primera matrícula en el programa de doctorado             : chr [1:19] "2024-25" "2023-24" "2022-23" "2023-24" ...
 $ Puntos: Curso de la primera matrícula en el programa de doctorado     : num [1:19] 0 0 0 NA NA NA NA NA NA NA ...
 $ Comentarios: Curso de la primera matrícula en el programa de doctorado: logi [1:19] NA NA NA NA NA NA ...
 $ Cuéntanos tu experiencia con R                                        : chr [1:19] "He usado R ya en anteriores ocasiones, tengo una pequeña base de conocimientos y estoy familiarizada con el lenguaje." "Usé R en el máster y para hacer algunos gráficos durante el principio de la tesis, pero me falta mucha segurida"| __truncated__ "Para mi tesis en el programa he debido desarrollar un Tn-seq, personalmente me ha costado horas de trabajo crea"| __truncated__ "He utilizado R en algunas asignaturas del grado y del máster, pero de una manera superficial." ...
 $ Puntos: Cuéntanos tu experiencia con R                                : num [1:19] 0 0 0 NA NA NA NA NA NA NA ...
 $ Comentarios: Cuéntanos tu experiencia con R                           : logi [1:19] NA NA NA NA NA NA ...
 $ Observaciones                                                         : chr [1:19] NA NA NA NA ...
 $ Puntos: Observaciones                                                 : num [1:19] 0 0 0 NA NA NA NA NA NA NA ...
 $ Comentarios: Observaciones                                            : logi [1:19] NA NA NA NA NA NA ...
#View (datos_curso) # Se nos abre en Rstudio
colnames(datos_curso) # Miramos las columnas que tiene
 [1] "Id"                                                                    
 [2] "Hora de inicio"                                                        
 [3] "Hora de finalización"                                                  
 [4] "Correo electrónico"                                                    
 [5] "Nombre"                                                                
 [6] "Comentarios: DNI o documento"                                          
 [7] "Curso de la primera matrícula en el programa de doctorado"             
 [8] "Puntos: Curso de la primera matrícula en el programa de doctorado"     
 [9] "Comentarios: Curso de la primera matrícula en el programa de doctorado"
[10] "Cuéntanos tu experiencia con R"                                        
[11] "Puntos: Cuéntanos tu experiencia con R"                                
[12] "Comentarios: Cuéntanos tu experiencia con R"                           
[13] "Observaciones"                                                         
[14] "Puntos: Observaciones"                                                 
[15] "Comentarios: Observaciones"                                            
datos_curso_filtrado <- datos_curso |>
  rename (curso = "Curso de la primera matrícula en el programa de doctorado",
         experiencia = "Cuéntanos tu experiencia con R",
         comentarios = "Observaciones",
         email = "Correo electrónico") |>
  select (curso, experiencia, comentarios, email) |>
  mutate (email = str_replace(email, "anonymous", "anónimo")) |>
  filter (curso == "2022-23") # filtrado y sin filtrar otra diapo.
  
words <- tolower(
                unlist(
                  strsplit(datos_curso_filtrado$experiencia, " ") ) ) 

words <- gsub("[[:punct:][:digit:]]", "",words)
stop_es <- stopwords("spanish")
words_clean <- words[!words %in% stop_es]

# Plot
wordcloud2(data=table(words_clean), size=0.5,
           color = rep(RColorBrewer::brewer.pal(11, "PRGn"), 20))

Ejercicios manejo de datos

Visualización

Para entender patrones y relaciones

Para comunicar resultados de forma clara

Para detectar errores o valores atípicos

Porque una imagen resume más que una tabla

Matetja et al

Colores

Colores… https://www.fabiocrameri.ch/colourmaps/

Desde gráficos muy simples

plot(x=rnorm(100), y=rnorm(100))

Hasta gráficos muy complejos

quién habla con quién en “The Office”

Código

Base R vs ggplot

plot(datos_limpios_kg$bill_length_mm, datos_limpios_kg$bill_depth_mm)

ggplot(datos_limpios_kg, aes(x = bill_length_mm, y = bill_depth_mm)) +
  geom_point()

De simple

ggplot(data = datos_limpios_kg, aes(x = bill_length_mm, y = bill_depth_mm)) + geom_point()

A complejo

library(ggplot2)

p <- ggplot(data = penguins, aes(x = bill_length_mm, y = bill_depth_mm)) +
geom_point(aes(color = body_mass_g), alpha = .6) +
  scico::scale_color_scico(palette = "bamako", direction = -1)+ 
## custom axes scaling
scale_x_continuous(breaks = 3:6 * 10, limits = c(30, 60)) +
scale_y_continuous(breaks = seq(12.5, 22.5, by = 2.5), limits = c(12.5, 22.5)) +
## custom labels
labs(
title = 'Bill Dimensions of Brush-Tailed Penguins (Pygoscelis)',
subtitle = 'A scatter plot of bill depth versus bill length.',
caption = 'data = Gorman, Williams & Fraser (2014) PLoS ONE, graph = Cédric Scherer',
x = 'Bill Length (mm)',
y = 'Bill Depth (mm)',
color = 'Body mass (g)'
) + theme_minimal () 
p

Bases de ggplot

ggplot = Grammar of Graphics (GG) Separa cada componente de un gráfico en componentes individuales, creando distintas capas. Así tenemos resultados más flexibles y personalizables.

  • Data
  • Aesthetics (x, y)
  • Geometries (points, lines, boxplots)
  • Scales (eg. colores)
  • Facets (subplots)
  • Statistics (show means, counts, and other statistical summaries of data)
  • Coordinates
  • Theme (apariencia general del gráfico)

layers

Ejemplo de capas

Hertz & McNeill 2024

Aesthetics

  • x, y
  • colour: color de los geoms
  • fill: el color de dentro de los geoms
  • group: a qué grupo pertenece un geom
  • shape: la forma de los puntos
  • linetype: tipo de linea (solid, dashed, etc)
  • size: escalar el tamaño
  • alpha: transparencia

Geometries

Categórico

  • geom_bar(): bar charts for categorical x axis
  • geom_boxplot(): box and whiskers plot for categorical variables
  • geom_violin(): distribution kernel of data dispersion

Continuo

  • geom_histogram(): histogram for continuous x axis
  • geom_point(): scatterplot
  • geom_line(): lines connecting points by increasing value of x
  • geom_path(): lines connecting points in sequence of appearance

Scales

Scales tambien pueden modificar el tipo de variable, transformarla etc. pero nos quedaremos con la espeficicacion del color.

scale_fill_manual ( values = c(“color1”, “color2”))

scale The name of the primary aesthetic (e.g., colour, shape or x) The name of the scale (e.g., continuous, discrete, brewer).

Facets

  • facet_wrap()
  • facet_grid()

facet_wrap(~ variable) | Divide el gráfico en varios paneles según una sola variable. Coloca los paneles en una cuadrícula flexible (se “envuelven” automáticamente).

facet_grid(fila ~ columna) | Divide los gráficos según dos variables: una define las filas y otra las columnas.

Themes

geeksforgeeks

Empecemos a visualizar

Creamos una base de datos simple sobre tipos de comida y cuanto cuestan

dat <- data.frame(
  meal = factor(c("Breakfast", "Lunch", "Dinner", "Dinner", "Lunch")),
  total_bill = c(4.89, 7.23, 15.34, 21.23, 11.32)
)

head(dat)
       meal total_bill
1 Breakfast       4.89
2     Lunch       7.23
3    Dinner      15.34
4    Dinner      21.23
5     Lunch      11.32

GRÁFICOS CATEGÓRICOS

Gráfico de Barras

Geom_bar por defecto nos cuenta el numero de datos

dat <- data.frame(
  meal = factor(c("Breakfast", "Lunch", "Dinner", "Dinner", "Lunch")),
  total_bill = c(4.89, 7.23, 15.34, 21.23, 11.32)
)

dat %>%
  ggplot(aes(x=meal)) +
  geom_bar() + theme_minimal()

Gráfico de Barras

Para que nos haga la suma, necesitamos “identity”

library(ggplot2)
library(dplyr)

dat <- data.frame(
  meal = factor(c("Breakfast", "Lunch", "Dinner", "Dinner", "Lunch")),
  total_bill = c(4.89, 7.23, 15.34, 21.23, 11.32)
)

# Very basic bar graph
dat %>%
ggplot(aes(x=meal, y=total_bill)) +
    geom_bar(stat="identity")

Ahora la media

dat %>%
ggplot(aes(x=meal, y=total_bill)) +
    geom_bar(stat="summary", fun = "mean")

CAMBIAMOS LOS COLORES

Queremos el tipo de comida en distintos colores Para un barplot en aestetics, argumento fill vamos a cambiar el “relleno” segun el grupo que indiquemos

dat %>%
ggplot(aes(x=meal, y=total_bill, fill=meal)) +
    geom_bar(stat="identity")

¿Qué pasa si modificamos “color”?

dat %>%
ggplot(aes(x=meal, y=total_bill, color=meal)) +
    geom_bar(stat="identity")

Sin leyenda -> guides (fill=FALSE)

dat %>%
ggplot(aes(x=meal, y=total_bill, fill=meal)) +
    geom_bar(stat="identity") +
    guides(fill=FALSE)

Añadimos el labs y theme

dat %>%
ggplot(aes(x=meal, y=total_bill, fill=meal)) +
    geom_bar(stat="identity") +
    guides(fill=FALSE) +
    labs(title = "Precio medio por comida", 
       subtitle = "Ajustado para dos personas",
       x = "Tipo de comida",
       y = "Precio") + theme_minimal()

Incluismos una variable más

dat <- data.frame(
  diet = factor(c("Vegan", "Carnivore", "Carnivore","Carnivore","Vegan", "Vegan", "Vegan")),
  meal = factor(c("Breakfast", "Dinner", "Breakfast","Lunch", "Dinner", "Dinner", "Lunch")),
  total_bill = c(5.32, 19.22, 4.89, 7.23, 15.34, 21.23, 11.32))

# Stacked bar graph -- queremos esto?
dat %>% 
  ggplot(aes(x=meal, y=total_bill, fill=diet)) +
  geom_bar(stat="identity")

position_dodge()

dat %>%
ggplot(aes(x=meal, y=total_bill, fill=diet)) +
    geom_bar(stat="identity", position=position_dodge()) 

SCALES

Más gráficos para datos categóricos

Boxplot

datos_limpios_kg %>% 
ggplot (aes(x = island, y = bill_length_mm)) + geom_boxplot()

Take care with boxplot

Seven distributions of data, shown as raw data points (of strip-plots), as box plots, and as violin plots

Fuente

Violin plot

violin_plot <- datos_limpios_kg %>% 
  ggplot (aes(x = island, y = bill_length_mm, fill = island)) + 
  geom_jitter (size=3, alpha=0.1, fill="grey") +
  #geom_jitter (aes(fill=island), size=5, alpha=0.2, fill="grey") +
  geom_violin(alpha=0.8) +  guides (fill= FALSE)+
  scale_fill_manual(values=c("#72874EFF", "#FED789FF", "#023743FF")) +
  theme_light() + theme(rect = element_rect(fill = "transparent"))

violin_plot
ggsave("images/violin_plot.png", violin_plot, width = 20, height = 20, units = "cm", scale = .5)

PNG guardado

Violines ggsave nos permite guardar en “eps”, “ps”, “tex” (pictex), “pdf”, “jpeg”, “tiff”, “png”, “bmp”, “svg” or “wmf”

DATOS CONTINUOS

Histogramas

Volvemos con los pingüinos

ggplot(datos_limpios_kg, 
       aes(x = flipper_length_mm)) + 
  geom_histogram() # bins=5

Histogramas

Volvemos con los pingüinos

ggplot(datos_limpios_kg, 
       aes(x = flipper_length_mm)) + geom_density(bw=1)

Scatterplots

datos_limpios_kg %>% 
  ggplot (aes(x = flipper_length_mm, y = bill_length_mm)) + 
  geom_point() 

Scales

datos_limpios_kg %>% 
  ggplot (aes(x = flipper_length_mm, y = bill_length_mm, colour = island)) + 
  scale_colour_manual(values=c("#72874EFF", "#FED789FF", "#023743FF")) +
  geom_point() + theme_light() 

Continous colors

datos_limpios_kg %>% 
  ggplot (aes(x = flipper_length_mm, y = bill_length_mm, colour = bill_length_mm)) + 
  geom_point() + theme_light() 

Continous colors

datos_limpios_kg %>% 
  ggplot (aes(x = flipper_length_mm, y = bill_length_mm, colour = bill_length_mm)) + 
  geom_point() +
  scale_color_gradientn(colors = c("#00AFBB", "#E7B800", "#FC4E07"))+
  theme_light() 

Colores continuos de paletas (viridis)

datos_limpios_kg %>% 
  ggplot (aes(x = flipper_length_mm, y = bill_length_mm, colour = bill_length_mm)) + 
  geom_point() +
  scale_color_viridis_c(option = "plasma") + 
  theme_light() 

Colores continuos de paletas (RColorBrewer)

datos_limpios_kg %>% 
  ggplot (aes(x = flipper_length_mm, y = bill_length_mm, colour = bill_length_mm)) + 
  geom_point() +
  scale_color_distiller(palette = "Spectral") +  # otras: "RdYlBu", "YlGnBu", "PuOr", etc.
  theme_light() 

STATISTICS

datos_limpios_kg %>% 
  ggplot (aes(x = flipper_length_mm, y = bill_length_mm)) + 
  geom_point() + geom_smooth()
# Method = "loess"

LINEAR MODELS

datos_limpios_kg %>% 
  ggplot (aes(x = flipper_length_mm, y = bill_length_mm)) + 
  geom_point() + theme_light() + geom_smooth(method = "lm")

LINEAR MODELS WITH INTERACTION

datos_limpios_kg %>% 
  ggplot (aes(x = flipper_length_mm, y = bill_length_mm, colour = island)) + 
  scale_colour_manual(values=c("#72874EFF", "#FED789FF", "#023743FF")) +
  geom_point() + theme_light() + geom_smooth(method = "lm")

facets

datos_limpios_kg %>% 
  ggplot (aes(x = flipper_length_mm, y = bill_length_mm, colour = island)) + 
  scale_colour_manual(values=c("#72874EFF", "#FED789FF", "#023743FF")) +
  geom_point() + theme_light() +  facet_wrap(~species, scales = "free")

facets

datos_limpios_kg %>% 
  ggplot (aes(x = flipper_length_mm, y = bill_length_mm, colour = island)) + 
  scale_colour_manual(values=c("#72874EFF", "#FED789FF", "#023743FF")) +
  geom_point() + theme_light() +  facet_wrap(species~island, scales = "free")

facet grid

datos_limpios_kg %>% 
  ggplot (aes(x = flipper_length_mm, y = bill_length_mm, colour = island)) + 
  scale_colour_manual(values=c("#72874EFF", "#FED789FF", "#023743FF")) +
  geom_point() + theme_light() +  facet_grid(species~island, scales = "free")

Mapas I

Mapa del crecimiento urbano en Valencia

Código

Mapas II

Apicultores en Alemania Código

Nuestros datos georeferenciados

library(rgbif)
library(ggplot2)
library(rnaturalearth)
library(sf)

datos <- occ_search(scientificName = "Lynx pardinus", limit = 100)
df <- datos$data

# Basic map
world <- ne_countries(scale = "medium", returnclass = "sf")

# Plot
ggplot() +
  geom_sf(data = world, fill = "grey90", color = "grey30") +
  geom_point(data = df, aes(x = decimalLongitude, y = decimalLatitude),
             color = "darkred", size = 2, alpha = 0.7) +
  coord_sf() +
  theme_minimal()
# Basic map
spain <- ne_countries(country=c("spain", "portugal"))
ggplot() +
  geom_sf(data = spain, fill = "grey99", color = "grey30") +
  geom_point(data = df, aes(x = decimalLongitude, y = decimalLatitude),
             color = "darkgreen", size = 2, alpha = 0.7) +
  coord_sf() +
  theme_minimal()

Mapa plantas

library(FloraIberica)

map_distribution(genus = "Lavandula", species = "stoechas", size = 0.9)

Visualizaciones geniales (ya no solo con r)

Valentina de Filippo

Bibliografía

Valentina de Filippo Tips tables

http://r.qcbs.ca/workshop03/book-en/grammar-of-graphics-gg-basics.html

https://link.springer.com/book/10.1007/0-387-28695-0

https://r-graphics.org/RECIPE-QUICK-SCATTER.html

https://www.geeksforgeeks.org/r-language/themes-and-background-colors-in-ggplot2-in-r/

Ejercicios

….